//
// Created by ZhangYuZhu on 2025/9/30.
//
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include <float.h>
#include "zJSON.h"

/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */
#ifndef isinf
#define isinf(d) (isnan((d - d)) && !isnan(d))
#endif
#ifndef isnan
#define isnan(d) (d != d)
#endif

static bool zJSON_add( zJSONFormatted* const item, const char* content)
{
    const size_t left_space = item->length - item->offset;
    if ( left_space <= 0 )
        return false;

    const int len = snprintf(item->content + item->offset, left_space, "%s", content);
    item->offset = item->offset + len ;
    if( len >= left_space )
        return false;
    return true;
}

static bool zJSON_addPrint(zJSONFormatted* const item, const char* format, ...)
{
    va_list args;
    va_start(args, format);

    while (*format != '\0')
    {
        const size_t left_space = item->length - item->offset;
        if ( left_space <= 0 )
            break;

        if (*format == '%')
        {
            format++; // 移动到占位符的下一个字符
            if (*format == 'd')
            {
                const int value = va_arg(args, int);
                const int len = snprintf(item->content + item->offset, left_space, "%d", value);
                item->offset = item->offset + len ;
                if( len >= left_space )
                    break;
            }
            else if (*format == 'f')
            {
                const double value = va_arg(args, double);
                const int len = snprintf(item->content + item->offset, left_space, "%f", value);
                item->offset = item->offset + len ;
                if( len >= left_space )
                    break;
            }
            else if (*format == 's')
            {
                char* value = va_arg(args, char*);
                const int len = snprintf(item->content + item->offset, left_space, "%s", value);
                item->offset = item->offset + len ;
                if( len >= left_space )
                    break;
            }
            else if (*format == 'c')
            {
                const int value = va_arg(args, int);
                const int len = snprintf(item->content + item->offset, left_space, "%c", value);
                item->offset = item->offset + len ;
                if( len >= left_space )
                    break;
            }
            else
            {
                printf("Unsupported format specifier: %c", *format);
                break;
            }
        }
        else
        {
            const int len = snprintf(item->content + item->offset, left_space, "%c", *format);
            item->offset = item->offset + len ;
            if( len >= left_space )
                break;
        }

        format++; // 移动到下一个字符
    }

    va_end(args);
    return true;
}

static bool zJSON_addDepth(zJSONFormatted* const item, size_t depth)
{
    for(int i = 0; i < depth; i++)
    {
        const size_t left_space = item->length - item->offset;
        if ( left_space <= 0 )
            return false;

        const int len = snprintf(item->content + item->offset, left_space, "\t");
        item->offset += len;
        if( len >= left_space )
            return false;
    }
    return true;
}

bool zJSON_IsFirstContent(const zJSONFormatted* const item)
{
    if(item->offset > 0)
    {
        if(*(item->content + item->offset - 1) == '{')
            return true;

        if(*(item->content + item->offset - 1) == '[')
            return true;
    }
    return false;
}


/*
 *
 */
static bool zJSON_addNameToObject(zJSONFormatted * const object, const char * const name)
{
    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Object;
        zJSON_add(object, "{");
    }

    if (!zJSON_IsFirstContent(object))
    {
        zJSON_add(object, ",");
    }

    if(object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }

    //zJSON_addPrint(object, "\"%s\":", name);
    zJSON_add(object, "\"");
    zJSON_add(object, name);
    zJSON_add(object, "\":");
    if(object->fmt)
        zJSON_add(object, " ");
    //zJSON_add(object, "true");

    if (object->offset >= object->length)
        return false;

    return true;
}



zJSON_PUBLIC(bool) zJSON_AddNullToObject(zJSONFormatted * const object, const char * const name)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_add(object, "null");

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddTrueToObject(zJSONFormatted * const object, const char * const name)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_add(object, "true");

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddFalseToObject(zJSONFormatted * const object, const char * const name)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_add(object, "false");

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddBoolToObject(zJSONFormatted * const object, const char * const name, const bool boolean)
{
    if (boolean )
    {
        return zJSON_AddTrueToObject(object, name);
    }
    return zJSON_AddTrueToObject(object, name);
}

/* securely comparison of floating-point variables */
static bool compare_double(double a, double b)
{
    double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
    return (fabs(a - b) <= maxVal * DBL_EPSILON);
}
extern unsigned char get_decimal_point(void);


static bool print_Number(zJSONFormatted * const object,const double number)
{
    if (object == NULL )
    {
        return false;
    }

    //unsigned char output_pointer[60] = {0};
    double d = number;
    int length = 0;
    size_t i = 0;
    char number_buffer[26] = {0}; /* temporary buffer to print the number into */
    unsigned char decimal_point = get_decimal_point();
    double test = 0.0;

    /* This checks for NaN and Infinity */
    if (isnan(d) || isinf(d))
    {
        length = sprintf((char*)number_buffer, "null");
    }
    else if(d == (double)((int)number))
    {
        length = sprintf((char*)number_buffer, "%d", (int)number);
    }
    else
    {
        /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
        length = sprintf((char*)number_buffer, "%1.15g", d);

        /* Check whether the original double can be recovered */
        if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d))
        {
            /* If not, print with 17 decimal places of precision */
            length = sprintf((char*)number_buffer, "%1.17g", d);
        }
    }

    /* sprintf failed or buffer overrun occurred */
    if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1)))
    {
        return false;
    }

    /* reserve appropriate space in the output */
    // output_pointer = ensure(output_buffer, (size_t)length + sizeof(""));
    // if (output_pointer == NULL)
    // {
    //     return false;
    // }

    /* copy the printed number to the output and replace locale
     * dependent decimal point with '.' */
    for (i = 0; i < ((size_t)length); i++)
    {
        if (number_buffer[i] == decimal_point)
        {
            number_buffer[i] = '.';
            continue;
        }

        //output_pointer[i] = number_buffer[i];
    }
    //output_pointer[i] = '\0';
    zJSON_add(object, number_buffer);
    //output_buffer->offset += (size_t)length;
    //zJSON_addPrint(object, "%f", number);
    if (object->offset >= object->length)
        return false;

    return true;
}


zJSON_PUBLIC(bool) zJSON_AddNumberToObject(zJSONFormatted * const object, const char * const name, const double number)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    print_Number(object, number);
    //zJSON_addPrint(object, "%f", number);
    if (object->offset >= object->length)
        return false;

    return true;
}
zJSON_PUBLIC(bool) zJSON_AddStringToObject(zJSONFormatted * const object, const char * const name, const char * const string)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_addPrint(object, "\"%s\"", string);

    if (object->offset >= object->length)
        return false;

    return true;
}
zJSON_PUBLIC(bool) zJSON_AddRawToObject(zJSONFormatted * const object, const char * const name, const char * const raw)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_addPrint(object, "%s", raw);

    if (object->offset >= object->length)
        return false;

    return true;
}
zJSON_PUBLIC(bool) zJSON_AddObjectStartToObject(zJSONFormatted * const object, const char * const name)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_add(object, "{");
    object->depth += 1;

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddObjectEndToObject(zJSONFormatted * const object)
{
    if (object == NULL   )
    {
        return false;
    }

    object->depth -= 1;

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }

    zJSON_add(object, "}");

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddArrayStartToObject(zJSONFormatted * const object, const char * const name)
{
    if (object == NULL || name == NULL )
    {
        return false;
    }

    zJSON_addNameToObject(object, name);
    zJSON_add(object, "[");
    object->depth += 1;

    if (object->offset >= object->length)
        return false;

    return true;
}

zJSON_PUBLIC(bool) zJSON_AddArrayEndToObject(zJSONFormatted * const object )
{
    if (object == NULL  )
    {
        return false;
    }

    object->depth -= 1;
    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "]");

    if (object->offset >= object->length)
        return false;

    return true;
}

/*
 * @brief
 *
 */
zJSON_PUBLIC(bool) zJSON_AddTrueToArray(zJSONFormatted* const object )
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "true");

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddFalseToArray(zJSONFormatted* const object )
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "false");

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddBoolToArray(zJSONFormatted* const object, const bool boolean)
{
    if (boolean)
        zJSON_AddTrueToArray(object);
    return zJSON_AddFalseToArray(object);
}

zJSON_PUBLIC(bool) zJSON_AddNumberToArray(zJSONFormatted* const object, const double number)
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    print_Number(object, number);
    //zJSON_addPrint(object, "%f", number);

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddStringToArray(zJSONFormatted* const object, const char* const string)
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_addPrint(object, "\"%s\"", string);

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddRawToArray(zJSONFormatted* const object, const char* const raw)
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_addPrint(object, "%s", raw);

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddObjectStartToArray(zJSONFormatted* const object)
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    object->depth++;
    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "{");

    if (object->offset >= object->length)
        return false;
    return true;
}
zJSON_PUBLIC(bool) zJSON_AddObjectEndToArray(zJSONFormatted* const object )
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    object->depth--;
    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "}");

    if (object->offset >= object->length)
        return false;
    return true;
}
zJSON_PUBLIC(bool) zJSON_AddArrayStartToArray(zJSONFormatted* const object )
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    object->depth++;
    if (!zJSON_IsFirstContent(object))
        zJSON_add(object, ",");

    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "[");

    if (object->offset >= object->length)
        return false;
    return true;
}

zJSON_PUBLIC(bool) zJSON_AddArrayEndToArray(zJSONFormatted* const object )
{
    if (object == NULL  )
    {
        return false;
    }

    if(object->type == zJSON_Raw)
    {
        object->type = zJSON_Array;
        zJSON_add(object, "[");
    }

    object->depth--;
    if (object->fmt)
    {
        zJSON_add(object, "\r\n");
        zJSON_addDepth(object, object->depth);
    }
    zJSON_add(object, "}");

    if (object->offset >= object->length)
        return false;
    return true;
}

/* Render a zJSON entity to text for transfer/storage. */
zJSON_PUBLIC(zJSONFormatted) zJSON_CreateFormatted(char* content, const size_t length)
{
    return (zJSONFormatted){
        .type = zJSON_Raw,
        .content = content,
        .length = length,
        .offset = 0,
        .depth = 1,
        .fmt = true
    };
}
/* Render a zJSON entity to text for transfer/storage without any formatting. */
zJSON_PUBLIC(zJSONFormatted) zJSON_CreateUnformatted(char* content, const size_t length)
{
    return (zJSONFormatted){
        .type = zJSON_Raw,
        .content = content,
        .length = length,
        .offset = 0,
        .depth = 1,
        .fmt = false
    };
}